﻿Option Strict On
Imports System.Runtime.InteropServices
Imports System.Timers

Namespace BMD2Net

    ''' <summary>
    ''' Namespace containing classes and enumerations to support the .Net API for the BMD2 library
    ''' </summary>
    <System.Runtime.CompilerServices.CompilerGeneratedAttribute> _
    Public Class clsNamespaceDoc
    End Class

    ''' <summary>
    ''' .Net wrapper around the BMD2 library written in C++ by Alex Comer for Tim Wool, USEPA Region IV
    ''' </summary>
    ''' <remarks></remarks>
    Public Class clsBMD2

#Region "Custom exception that returns last error message"

        ''' <summary>
        ''' This custom exception is used locally to retrieve and display the last error generated by the BMD2 library
        ''' </summary>
        Private Class BMD2Exception
            Inherits System.Exception

            Public Sub New()
                MyBase.New("The following error occurred while accessing the BMD2 file: " & GetLastError())
            End Sub
        End Class

#End Region

#Region "DLL Imports (private)"

        <DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
        Private Shared Function SetDllDirectory(ByVal lpPathName As String) As Boolean
        End Function

        <DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
        Private Shared Function LoadLibrary(<MarshalAsAttribute(UnmanagedType.LPStr)> ByVal PathName As System.Text.StringBuilder) As IntPtr
        End Function

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getlasterror(ByRef buffer_size As UInt32,
                                            <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal message As System.Text.StringBuilder)
        End Sub

        Private Enum enumError As UInt32
            None
            Warning = 256
            CANNOT_OPEN = 512
            INVALID_FILE
            INVALID_HANDLE
            CANNOT_CLOSE
            CANNOT_FLUSH
            READ_FAILED
            WRITE_FAILED
            SEEK_FAILED
            INVALID_INDEX
            KEY_NOT_FOUND
            INVALID_PARAMETER
            INVALID_BUFFER
            INVALID_MOMENT
            INVALID_NAME
            INVALID_FILTER
            INVALID_DOMAIN
            CANNOT_MAP_VIEW
            INVALID_MODE
            VIEW_RANGE_ERROR
            INVALID_OPERATION
            UNKNOWN = 1024
            MEMORY_EXHAUSTED
        End Enum

        Private Shared Function GetLastError() As String
            Dim sb As New System.Text.StringBuilder(512)
            bmd2getlasterror(CUInt(sb.Capacity), sb)
            Return sb.ToString
        End Function

        ''' <summary>
        ''' The optimization type to use when creating or opening a file
        ''' </summary>
        ''' <remarks>
        ''' <seealso cref="Open" />
        ''' <seealso cref="Create" />
        ''' </remarks>
        Public Enum enumOptimization As UInt32
            ''' <summary>Optimization is disabled (data retrieval will be slower but file will be 1/2 the size)</summary>
            None
            ''' <summary>In-the-fly optimization is performed while writing using <c>SetNextFrame</c></summary>
            OnTheFly
            ''' <summary>Optimization is performed after call to <c>EndFrames</c></summary>
            OnClose
            ''' <summary>Optimization is performed when file is opened (do not use when creating)</summary>
            OnRead
        End Enum

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2create(<MarshalAsAttribute(UnmanagedType.LPStr)> ByVal filename As System.Text.StringBuilder,
                                      ByRef num_segments As UInt32,
                                      ByRef num_variables As UInt32,
                                      ByRef num_moments As UInt32,
                                      ByRef year As UInt32,
                                      ByRef month As UInt32,
                                      ByRef day As UInt32,
                                      ByRef hour As UInt32,
                                      ByRef minute As UInt32,
                                      ByRef second As UInt32,
                                      ByRef optimization As enumOptimization,
                                      ByRef handle As UInt32,
                                      ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2open(<MarshalAsAttribute(UnmanagedType.LPStr)> ByVal filename As System.Text.StringBuilder,
                                    ByRef optimization As enumOptimization,
                                    ByRef handle As UInt32,
                                    ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2close(ByRef handle As UInt32,
                                     ByRef errcode As UInt32)
        End Sub

        ''' <summary>
        ''' The logging level threshold of the session
        ''' </summary>
        ''' <remarks><seealso cref="LogLevelThreshold" /></remarks>
        Public Enum enumLogLevelThreshold As UInt32
            ''' <summary>Logging level thresholds No logging</summary>
            None = 0
            ''' <summary>Logging level thresholds No logging</summary>
            [Error]
            ''' <summary>Log only errors</summary>
            Warning
            ''' <summary>Log errors, warnings, and informational messages</summary>
            Info
            ''' <summary>Log errors, warnings, informational message, and debugging information (output will be voluminous)</summary>
            Debug
        End Enum

        <DllImport("bmd2.dll", CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2setloglevelthreshold(ByRef hl_loglevelthreshold As enumLogLevelThreshold,
                                                    ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getloglevelthreshold(ByRef hl_loglevelthreshold As enumLogLevelThreshold)
        End Sub

        Public Enum enumIndexBase As UInt32
            Zero
            One
        End Enum

        <DllImport("bmd2.dll", CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2setindexbase(ByRef base As enumIndexBase,
                                            ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getindexbase(ByRef base As enumIndexBase)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2setproperty(<MarshalAsAttribute(UnmanagedType.LPStr)> ByVal key As System.Text.StringBuilder,
                                           <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal value As System.Text.StringBuilder,
                                           ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getproperty(<MarshalAsAttribute(UnmanagedType.LPStr)> ByVal key As System.Text.StringBuilder,
                                           ByRef buffer_size As UInt32,
                                           <[Out](), MarshalAs(UnmanagedType.LPStr)> ByVal value As System.Text.StringBuilder,
                                           ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getnumproperties(ByRef num As UInt32,
                                                ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getnthproperty(ByRef property_index As UInt32,
                                              ByRef key_buffer_size As UInt32,
                                              ByRef value_buffer_size As UInt32,
                                              <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal key As System.Text.StringBuilder,
                                              <[Out](), MarshalAs(UnmanagedType.LPStr)> ByVal value As System.Text.StringBuilder,
                                              ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2setmetadata(ByRef handle As UInt32,
                                           <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal key As System.Text.StringBuilder,
                                           <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal value As System.Text.StringBuilder,
                                           ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getmetadata(ByRef handle As UInt32,
                                           <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal key As System.Text.StringBuilder,
                                           ByRef buffer_size As UInt32,
                                           <[Out](), MarshalAs(UnmanagedType.LPStr)> ByVal value As System.Text.StringBuilder,
                                           ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getnummetadata(ByRef handle As UInt32,
                                              ByRef num As UInt32,
                                              ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getnthmetadata(ByRef handle As UInt32,
                                              ByRef metadata_index As UInt32,
                                              ByRef key_buffer_size As UInt32,
                                              ByRef value_buffer_size As UInt32,
                                              <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal key As System.Text.StringBuilder,
                                              <[Out](), MarshalAs(UnmanagedType.LPStr)> ByVal value As System.Text.StringBuilder,
                                              ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getseedmoment(ByRef handle As UInt32,
                                             ByRef buffer_size As UInt32,
                                             ByRef year As UInt32,
                                             ByRef month As UInt32,
                                             ByRef day As UInt32,
                                             ByRef hour As UInt32,
                                             ByRef minute As UInt32,
                                             ByRef second As UInt32,
                                             <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal description As System.Text.StringBuilder,
                                             ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getnummoments(ByRef handle As UInt32,
                                             ByRef num_moments As UInt32,
                                             ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getmoments(ByRef handle As UInt32,
                                          ByRef moments() As Double,
                                          ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getnthmoment(ByRef handle As UInt32,
                                            ByRef moment_index As UInt32,
                                            ByRef buffer_size As UInt32,
                                            ByRef year As UInt32,
                                            ByRef month As UInt32,
                                            ByRef day As UInt32,
                                            ByRef hour As UInt32,
                                            ByRef minute As UInt32,
                                            ByRef second As UInt32,
                                            ByRef elapsed_days As Double,
                                            <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal description As System.Text.StringBuilder,
                                            ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getnumsegments(ByRef handle As UInt32,
                                              ByRef numsegs As UInt32,
                                              ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getsegindexbyname(ByRef handle As UInt32,
                                                 <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal segname As System.Text.StringBuilder,
                                                 ByRef segment_index As UInt32,
                                                 ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2setsegmentmetadata(ByRef handle As UInt32,
                                                  ByRef segment_index As UInt32,
                                                  <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal key As System.Text.StringBuilder,
                                                  <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal value As System.Text.StringBuilder,
                                                  ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getsegmentmetadata(ByRef handle As UInt32,
                                                  ByRef segment_index As UInt32,
                                                  <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal key As System.Text.StringBuilder,
                                                  ByRef buffer_size As UInt32,
                                                  <[Out](), MarshalAs(UnmanagedType.LPStr)> ByVal value As System.Text.StringBuilder,
                                                  ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getnumsegmentmetadata(ByRef handle As UInt32, ByRef segment_index As UInt32, ByRef num As UInt32, ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getnthsegmentmetadata(ByRef handle As UInt32,
                                                     ByRef segment_index As UInt32,
                                                     ByRef metadata_index As UInt32,
                                                     ByRef key_buffer_size As UInt32,
                                                     ByRef value_buffer_size As UInt32,
                                                     <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal key As System.Text.StringBuilder,
                                                     <[Out](), MarshalAs(UnmanagedType.LPStr)> ByVal value As System.Text.StringBuilder,
                                                     ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getnumvariables(ByRef handle As UInt32,
                                               ByRef num As UInt32,
                                               ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getvarindexbyname(ByRef handle As UInt32,
                                                 <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal varname As System.Text.StringBuilder,
                                                 ByRef var_index As UInt32,
                                                 ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2setvariablemetadata(ByRef handle As UInt32,
                                                   ByRef variable_index As UInt32,
                                                   <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal key As System.Text.StringBuilder,
                                                   <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal value As System.Text.StringBuilder,
                                                   ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getvariablemetadata(ByRef handle As UInt32,
                                                   ByRef variable_index As UInt32,
                                                   <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal key As System.Text.StringBuilder,
                                                   ByRef buffer_size As UInt32,
                                                   <[Out](), MarshalAs(UnmanagedType.LPStr)> ByVal value As System.Text.StringBuilder,
                                                   ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getnumvariablemetadata(ByRef handle As UInt32,
                                                      ByRef variable_index As UInt32,
                                                      ByRef num As UInt32,
                                                      ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getnthvariablemetadata(ByRef handle As UInt32,
                                                      ByRef variable_index As UInt32,
                                                      ByRef metadata_index As UInt32,
                                                      ByRef key_buffer_size As UInt32,
                                                      ByRef value_buffer_size As UInt32,
                                                      <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal key As System.Text.StringBuilder,
                                                      <[Out](), MarshalAs(UnmanagedType.LPStr)> ByVal value As System.Text.StringBuilder,
                                                      ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2setnextframe(ByRef handle As UInt32,
                                            ByRef moment As Double,
                                            ByVal data(,) As Single,
                                            ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getdataattime(ByRef handle As UInt32,
                                             ByRef moment_index As UInt32,
                                             ByRef moment As Double,
                                             ByVal data(,) As Single,
                                             ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getdataovertime(ByRef handle As UInt32,
                                               ByRef segment_index As UInt32,
                                               ByRef variable_index As UInt32,
                                               ByVal data_x() As Double,
                                               ByVal data_y() As Single,
                                               ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getdataoversegment(ByRef handle As UInt32,
                                                  ByRef variable_index As UInt32,
                                                  ByRef moment_index As UInt32,
                                                  <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal domain_metadata_key As System.Text.StringBuilder,
                                                  <MarshalAsAttribute(UnmanagedType.LPStr)> ByVal segment_filter As System.Text.StringBuilder,
                                                  ByVal data_x() As Single,
                                                  ByVal data_y() As Single,
                                                  ByRef num_matching_segments As UInt32,
                                                  ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getminmaxforvariable(ByRef handle As UInt32,
                                                    ByRef variable_index As UInt32,
                                                    ByRef min As Single,
                                                    ByRef max As Single,
                                                    ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getminmaxforsegvar(ByRef handle As UInt32,
                                                  ByRef segment_index As UInt32,
                                                  ByRef variable_index As UInt32,
                                                  ByRef min As Single,
                                                  ByRef max As Single,
                                                  ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2beginframes(ByRef handle As UInt32,
                                       ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2endframes(ByRef handle As UInt32,
                                         ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2canceloptimization(ByRef handle As UInt32,
                                                  ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2getprogress(ByRef handle As UInt32,
                                           ByRef progress As UInt32,
                                           ByRef errcode As UInt32)
        End Sub

        <DllImport("bmd2.dll", charset:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Sub bmd2isoptimized(ByRef handle As UInt32,
                                           ByRef isoptimized As UInt32,
                                           ByRef errcode As UInt32)
        End Sub

#End Region

#Region "Utility methods (private)"

        ''' <summary>
        ''' Round date-time value to the nearest second
        ''' </summary>
        ''' <param name="Original">Value to be rounded</param>
        ''' <returns></returns>
        ''' <remarks>This is necessary because slight round-off errors when truncated will appear as, e.g., 11:59 rather than 12:00</remarks>
        Private Function RoundDateTime(ByVal Original As Date) As Date
            With Original
                If .Millisecond >= 500 Then Original = .AddSeconds(1)
                Return New Date(.Year, .Month, .Day, .Hour, .Minute, .Second)
            End With
        End Function

        ''' <summary>
        ''' Convert internal double time value to normal date
        ''' </summary>
        Private Function GetDate(ByVal TimeValue As Double) As Date
            Return RoundDateTime(SeedMoment.AddDays(TimeValue))
        End Function

        ''' <summary>
        ''' Convert date to internal double time value
        ''' </summary>
        Private Function GetMoment(ByVal DateTime As Date) As Double
            Static Seed As Date = SeedMoment
            Return DateTime.Subtract(Seed).TotalDays
        End Function

        ''' <summary>
        ''' Test object; if dbnull, nothing, empty string, or minimum value, return default value
        ''' </summary>
        Friend Function TestNull(Value As Object, DefaultValue As Object) As Object
            If IsDBNull(Value) OrElse
                IsNothing(Value) OrElse
                (TypeOf Value Is String AndAlso String.IsNullOrEmpty(CType(Value, String))) OrElse
                (TypeOf Value Is Date AndAlso CType(Value, Date) = DateTime.MinValue) OrElse
                (IsNumeric(Value) AndAlso CType(Value, Single) = Single.MinValue) Then Return DefaultValue Else Return Value
        End Function

#End Region

#Region "Public methods and properties..."

        Private filename As String = ""
        Private handle As UInt32 = 0 'is initially unassigned
        Private errcode As UInt32 = enumError.None
        Private isOpen As Boolean = False
        Private isCancelled As Boolean = False

        ''' <summary>
        ''' This event is raised after EndFrame is called to report progress during optimization when method set to ONCLOSE
        ''' </summary>
        ''' <param name="Status">Text indicating the current activity being performed</param>
        ''' <param name="PercentComplete">Integer percent complete (0 to 100)</param>
        Public Event Progress(Status As String, PercentComplete As Integer)

        ''' <summary>
        ''' Call this method to cancel the ongoing operation
        ''' </summary>
        Public Sub Cancel()
            isCancelled = True
        End Sub

        ''' <summary>
        ''' This method must be called immediately upon instatiation and before any BMD2 calls are made; it sets the appropriate subdirectory depending on whether is 32- or 64-bit process
        ''' </summary>
        Private Shared Sub SetDLLPath()
            Dim path As String = My.Application.Info.DirectoryPath & CStr(IIf(Environment.Is64BitProcess, "\x64", "\x86"))
            Dim dllfile As String = IO.Path.Combine(path, "bmd2.dll")
            If Not My.Computer.FileSystem.DirectoryExists(path) OrElse
               Not My.Computer.FileSystem.FileExists(dllfile) Then
                Throw New Exception("Unable to find BMD2 library: " & dllfile)
            End If
            If Not SetDllDirectory(path) Then
                Throw New Exception("Unable to set DLL Directory path to " & path)
            End If
        End Sub

        ''' <summary>
        ''' Instantiate class
        ''' </summary>
        Public Sub New()
            SetDLLPath()
        End Sub

        ''' <summary>
        ''' Instantiate class and open existing file
        ''' </summary>
        ''' <param name="Filename">Full path to .bmd2 file</param>
        ''' <param name="Optimized">If True, existing unoptimized file will be opened with OnRead optimization specified</param>
        Public Sub New(Filename As String, Optional Optimized As Boolean = False)
            MyBase.New()
            SetDLLPath()
            Open(Filename, Optimized)
        End Sub

        ''' <summary>
        ''' Instantiate class and create new file
        ''' </summary>
        ''' <param name="Filename">Full path to .bmd2 file</param>
        ''' <param name="NumSegments">Number of segments</param>
        ''' <param name="NumVariables">Number of variables</param>
        ''' <param name="NumMoments">Maximum number of moments expected to be stored in file</param>
        ''' <param name="SeedMoment">Seed moment to use when creating file</param>
        ''' <param name="Optimization">Optimization desired for new file</param>
        Public Sub New(Filename As String, NumSegments As Integer, NumVariables As Integer, NumMoments As Integer, SeedMoment As DateTime, Optimization As enumOptimization)
            MyBase.New()
            SetDLLPath()
            Create(Filename, NumSegments, NumVariables, NumMoments, SeedMoment, Optimization)
        End Sub

        Public Enum enumFileAccessMethod
            Mapped
            ChunkMapped
            StdIO
        End Enum

        ''' <summary>
        ''' Set the desired file access method; if used, this MUST be called before file is created
        ''' </summary>
        Public Shared Property FileAccessMethod As enumFileAccessMethod
            Set(value As enumFileAccessMethod)
                Dim s As String = ""
                Select Case value
                    Case enumFileAccessMethod.ChunkMapped : s = clsBMD2.FILE_ACCESS_METHOD_CHUNK_MAPPED
                    Case enumFileAccessMethod.Mapped : s = clsBMD2.FILE_ACCESS_METHOD_MAPPED
                    Case enumFileAccessMethod.StdIO : s = clsBMD2.FILE_ACCESS_METHOD_STDIO
                End Select
                clsBMD2.Property(clsBMD2.PROPERTY_BLOCK_ACCESS_METHOD) = s
                clsBMD2.Property(clsBMD2.PROPERTY_HEADER_ACCESS_METHOD) = s
            End Set
            Get
                Dim s As String = clsBMD2.Property(clsBMD2.PROPERTY_BLOCK_ACCESS_METHOD)
                Select Case s
                    Case clsBMD2.FILE_ACCESS_METHOD_CHUNK_MAPPED : Return enumFileAccessMethod.ChunkMapped
                    Case clsBMD2.FILE_ACCESS_METHOD_MAPPED : Return enumFileAccessMethod.Mapped
                    Case clsBMD2.FILE_ACCESS_METHOD_STDIO : Return enumFileAccessMethod.StdIO
                    Case Else
                        Throw New ApplicationException("Invalid property value returned for " & clsBMD2.PROPERTY_BLOCK_ACCESS_METHOD)
                End Select
            End Get
        End Property

        ''' <summary>
        ''' The type of algorithm to use when optimizing using OnClose
        ''' </summary>
        ''' <remarks><seealso cref="OnCloseAlgorithm" /></remarks>
        Public Enum enumOnCloseAlgorithm
            ByVariable
            ByMoment
        End Enum

        ''' <summary>
        ''' Set/get the desired ONCLOSE algorithm; only applies when <see cref="enumOptimization" /> is set to <c>OnClose</c>
        ''' <para>The default is ALGORITHM_BY_VARIABLE. Note however that using BMD2_OPTIMIZATION_ONTHEFLY implies ALGORITHM_BY_MOMENT. 
        ''' Also note that if ONCLOSEALGORITHM=BYVARIABLE, then TVS_VIEW_FRAMES will be PROPERTY_MAX_TVS_VIEW_FRAMES and VST_VIEW_FRAMES 
        ''' will be PROPERTY_MIN_VST_VIEW_FRAMES, whereas if ONCLOSEALGORITHM=BYMOMENT, then TVS_VIEW_FRAMES will be 
        ''' PROPERTY_MIN_TVS_VIEW_FRAMES and VST_VIEW_FRAMES will be PROPERTY_MAX_VST_VIEW_FRAMES 
        ''' (subject in all cases to the limit imposed by ENV_PROPERTY_MAX_VIEW_BUFFER_SIZE)</para>
        ''' </summary>
        Public Shared Property OnCloseAlgorithm As enumOnCloseAlgorithm
            Set(value As enumOnCloseAlgorithm)
                Dim s As String = ""
                Select Case value
                    Case enumOnCloseAlgorithm.ByVariable : s = clsBMD2.ALGORITHM_BY_VARIABLE
                    Case enumOnCloseAlgorithm.ByMoment : s = clsBMD2.ALGORITHM_BY_MOMENT
                End Select
                clsBMD2.Property(clsBMD2.PROPERTY_ONCLOSEALGORITHM) = s
            End Set
            Get
                Dim s As String = clsBMD2.Property(clsBMD2.PROPERTY_ONCLOSEALGORITHM)
                Select Case s
                    Case clsBMD2.ALGORITHM_BY_VARIABLE : Return enumOnCloseAlgorithm.ByVariable
                    Case clsBMD2.ALGORITHM_BY_MOMENT : Return enumOnCloseAlgorithm.ByMoment
                    Case Else
                        Throw New ApplicationException("Invalid property value returned for " & clsBMD2.PROPERTY_ONCLOSEALGORITHM)
                End Select
            End Get
        End Property

        ''' <summary>
        ''' Open existing BMD2 file for reading only
        ''' </summary>
        ''' <param name="Filename">Full path to .bmd2 file</param>
        ''' <param name="Optimize">Optimization flag; set to True only if file is unoptimized and you wish to do so OnRead</param>
        ''' <returns>True if successful</returns>
        ''' <remarks>IndexBase is always set to zero</remarks>
        Public Function Open(Filename As String, Optimize As Boolean) As Boolean
            If Not My.Computer.FileSystem.FileExists(Filename) Then Throw New IO.FileNotFoundException("File was not found.", Filename)
            Dim sb As New System.Text.StringBuilder(Filename & Chr(0), 512)
            bmd2open(sb, CType(IIf(Optimize, enumOptimization.OnRead, enumOptimization.None), enumOptimization), handle, errcode)
            If errcode <> enumError.None Then Throw New BMD2Exception
            isOpen = True

            Me.filename = Filename
            'LogLevelThreshold = enumLogLevelThreshold.Info

            IndexBase = enumIndexBase.Zero
            Return errcode = 0
        End Function

        ''' <summary>
        ''' Create new BMD2 file for writing only; if file already exists it will be overwritten
        ''' </summary>
        ''' <param name="Filename">Full path to .bmd2 file</param>
        ''' <param name="NumSegments">Number of segments</param>
        ''' <param name="NumVariables">Number of variables</param>
        ''' <param name="NumMoments">Maximum number of moments expected to be stored in file</param>
        ''' <param name="SeedMoment">The date-time for the seed moment</param>
        ''' <param name="Optimization">The desired optimization method</param>
        ''' <returns>True if successful</returns>
        ''' <remarks>
        ''' IndexBase set to Zero
        ''' </remarks>
        Public Function Create(Filename As String, NumSegments As Integer, NumVariables As Integer, NumMoments As Integer, SeedMoment As DateTime, Optimization As enumOptimization) As Boolean
            Dim sb As New System.Text.StringBuilder(Filename & Chr(0), 512)
            bmd2create(sb, CUInt(NumSegments), CUInt(NumVariables), CUInt(NumMoments),
                       CUInt(SeedMoment.Year), CUInt(SeedMoment.Month), CUInt(SeedMoment.Day),
                       CUInt(SeedMoment.Hour), CUInt(SeedMoment.Minute), CUInt(SeedMoment.Second),
                       Optimization, handle, errcode)
            If errcode <> enumError.None Then Throw New BMD2Exception
            isOpen = True

            Me.filename = Filename
            IndexBase = enumIndexBase.Zero

            Return errcode = 0
        End Function

        ''' <summary>
        ''' Close open BMD2 file
        ''' </summary>
        ''' <returns>True if successful</returns>
        Public Function Close() As Boolean
            If Not isOpen Then Return True
            Debug.Print("start close")
            bmd2close(handle, errcode)
            If errcode <> enumError.None Then Throw New BMD2Exception
            isOpen = False
            Debug.Print("end close")
            Return errcode = 0
        End Function

        ''' <summary>
        ''' Close then reopen BMD2 file
        ''' </summary>
        ''' <returns>True if successful</returns>
        Public Function Reopen() As Boolean
            Dim isOptimized As Boolean = False
            If isOpen Then
                isOptimized = isOptimized
                Close()
            End If
            Return Open(Me.filename, isOptimized)
        End Function

        ''' <summary>
        ''' Determine whether the file has already been optimized
        ''' </summary>
        ''' <returns>True if file is already optimized</returns>
        Public Function IsOptimized() As Boolean
            Dim isOpt As UInt32 = 0
            bmd2isoptimized(handle, isOpt, errcode)
            If errcode <> enumError.None Then Throw New BMD2Exception
            Return isOpt = 1
        End Function

        ''' <summary>
        ''' Specify what types of messages are sent to the log file called "BMDSession_yyyymddhhmm.log" placed in program folder (or can set environment variable to specify folder)
        ''' </summary>
        Public Shared Property LogLevelThreshold() As enumLogLevelThreshold
            Get
                SetDLLPath()
                Dim l As enumLogLevelThreshold
                bmd2getloglevelthreshold(l)
                Return l
            End Get
            Set(value As enumLogLevelThreshold)
                SetDLLPath()
                Dim errcode As UInt32
                bmd2setloglevelthreshold(value, errcode)
                If errcode <> enumError.None Then Throw New BMD2Exception
            End Set
        End Property

        ''' <summary>
        ''' Specify the base index for all arrays (e.g, One for Fortran, Zero for most others)
        ''' </summary>
        Public Shared Property IndexBase() As enumIndexBase
            Get
                SetDLLPath()
                Dim i As enumIndexBase
                bmd2getindexbase(i)
                Return i
            End Get
            Set(value As enumIndexBase)
                SetDLLPath()
                Dim errcode As UInt32
                bmd2setindexbase(value, errcode)
                If errcode <> enumError.None Then Throw New BMD2Exception
            End Set
        End Property

        ''' <summary>
        ''' Get the seed moment for a dataset
        ''' </summary>
        Public ReadOnly Property SeedMoment As DateTime
            Get
                Dim month, day, year, hour, minute, second As UInt32
                Dim Description As New System.Text.StringBuilder(512)
                bmd2getseedmoment(handle, CUInt(Description.Capacity), year, month, day, hour, minute, second, Description, errcode)
                If errcode <> enumError.None Then Throw New BMD2Exception
                Return New DateTime(CInt(year), CInt(month), CInt(day), CInt(hour), CInt(minute), CInt(second))
            End Get
        End Property

        'standard predefined property names and values recognized by API

        Public Const PROPERTY_MAXTHREADS = "MAXTHREADS"
        Public Const PROPERTY_MAXVIEWBUFFERSIZE = "MAXVIEWBUFFERSIZE"
        Public Const PROPERTY_MAXVSTVIEWFRAMES = "MAXVSTVIEWFRAMES"
        Public Const PROPERTY_MINVSTVIEWFRAMES = "MINVSTVIEWFRAMES"
        Public Const PROPERTY_MAXTVSVIEWFRAMES = "MAXTVSVIEWFRAMES"
        Public Const PROPERTY_MINTVSVIEWFRAMES = "MINTVSVIEWFRAMES"
        Public Const PROPERTY_ONCLOSEALGORITHM = "ONCLOSEALGORITHM"
        Public Const PROPERTY_HEADER_ACCESS_METHOD = "HEADERACCESSMETHOD"
        Public Const PROPERTY_BLOCK_ACCESS_METHOD = "BLOCKACCESSMETHOD"

        Public Const ALGORITHM_BY_VARIABLE = "BYVARIABLE"
        Public Const ALGORITHM_BY_MOMENT = "BYMOMENT"
        Public Const FILE_ACCESS_METHOD_MAPPED = "MAPPED"
        Public Const FILE_ACCESS_METHOD_CHUNK_MAPPED = "CHUNK_MAPPED"
        Public Const FILE_ACCESS_METHOD_STDIO = "STDIO"

        ''' <summary>
        ''' Session properties are used to control the library as a whole, including settings like number of threads, memory, etc.
        ''' </summary>
        ''' <value>Dictionary of string keys and values</value>
        Public Shared ReadOnly Property [Property] As Dictionary(Of String, String)
            Get
                SetDLLPath()
                Dim p As New Dictionary(Of String, String)
                Dim num As UInt32
                Dim errcode As UInt32
                bmd2getnumproperties(num, errcode)
                If errcode <> enumError.None Then Throw New BMD2Exception
                If num > 0 Then
                    For i As UInt32 = 0 To CUInt(num - 1)
                        Dim sbKey As New System.Text.StringBuilder(512)
                        Dim sbValue As New System.Text.StringBuilder(512)
                        bmd2getnthproperty(i, CUInt(sbKey.Capacity), CUInt(sbValue.Capacity), sbKey, sbValue, errcode)
                        If errcode <> enumError.None Then Throw New BMD2Exception
                        p.Add(sbKey.ToString, sbValue.ToString)
                    Next
                End If
                Return p
            End Get
        End Property

        ''' <summary>
        ''' Session properties are used to control the library as a whole, including settings like number of threads, memory, etc.
        ''' </summary>
        ''' <param name="Key">Key to associate with value</param>
        ''' <value>Value associated with key</value>
        ''' <remarks>When creating a file, all calls to set properties should be made before the first call to SetNextFrame</remarks>
        Public Shared WriteOnly Property [Property](Key As String) As String
            Set(value As String)
                SetDLLPath()
                Dim sbKey As New System.Text.StringBuilder(Key & Chr(0), 512)
                Dim sbValue As New System.Text.StringBuilder(value & Chr(0), 512)
                Dim errcode As UInt32
                bmd2setproperty(sbKey, sbValue, errcode)
                If errcode <> enumError.None Then Throw New BMD2Exception
            End Set
        End Property

        Public Const METADATA_MODEL_NAME = "MODEL"
        Public Const METADATA_COMMENT = "COMMENT"
        Public Const METADATA_SHAPEFILE = "SHAPEFILE"

        ''' <summary>
        ''' Metadata are associated with the dataset used to control the library as a whole, and may include predefined keys like METADATA*
        ''' </summary>
        ''' <value>Dictionary of string keys and values</value>
        Public ReadOnly Property MetaData As Dictionary(Of String, String)
            Get
                Dim m As New Dictionary(Of String, String)
                Dim num As UInt32
                bmd2getnummetadata(handle, num, errcode)
                If errcode <> enumError.None Then Throw New BMD2Exception
                If num > 0 Then
                    For i As UInt32 = 0 To CUInt(num - 1)
                        Dim sbKey As New System.Text.StringBuilder(512)
                        Dim sbValue As New System.Text.StringBuilder(512)
                        bmd2getnthmetadata(handle, i, CUInt(sbKey.Capacity), CUInt(sbValue.Capacity), sbKey, sbValue, errcode)
                        If errcode <> enumError.None Then Throw New BMD2Exception
                        m.Add(sbKey.ToString, sbValue.ToString)
                    Next
                End If
                Return m
            End Get
        End Property

        ''' <summary>
        ''' Metadata are associated with the dataset used to control the library as a whole, and may include predefined keys like METADATA*
        ''' </summary>
        ''' <param name="Key">Key to associate with value</param>
        ''' <value>Value associated with key</value>
        ''' <remarks>When creating a file, all calls to set metadata should be made before the first call to SetNextFrame</remarks>
        Public WriteOnly Property MetaData(Key As String) As String
            Set(value As String)
                Dim sbKey As New System.Text.StringBuilder(Key & Chr(0), 512)
                Dim sbValue As New System.Text.StringBuilder(value & Chr(0), 512)
                bmd2setmetadata(handle, sbKey, sbValue, errcode)
                If errcode <> enumError.None Then Throw New BMD2Exception
            End Set
        End Property

        ''' <summary>
        ''' Number of segments (read-only)
        ''' </summary>
        Public ReadOnly Property NumSegments As Integer
            Get
                Dim n As UInt32 = 0
                bmd2getnumsegments(handle, n, errcode)
                If errcode <> enumError.None Then Throw New BMD2Exception
                Return CInt(n)
            End Get
        End Property

        ''' <summary>
        ''' Number of variables (read-only)
        ''' </summary>
        Public ReadOnly Property NumVariables As Integer
            Get
                Dim n As UInt32 = 0
                bmd2getnumvariables(handle, n, errcode)
                If errcode <> enumError.None Then Throw New BMD2Exception
                Return CInt(n)
            End Get
        End Property

        ''' <summary>
        ''' Number of moments (read-only)
        ''' </summary>
        Public ReadOnly Property NumMoments As Integer
            Get
                Dim n As UInt32 = 0
                bmd2getnummoments(handle, n, errcode)
                If errcode <> enumError.None Then Throw New BMD2Exception
                Return CInt(n)
            End Get
        End Property

        ''' <summary>
        ''' Return list of all moments at which data are available
        ''' </summary>
        Public ReadOnly Property NthMoment(MomentIndex As Integer) As DateTime
            Get
                Dim sb As New System.Text.StringBuilder(512)
                Dim y, m, d, h, mm, s As UInt32
                Dim elapsed As Double = 0.0
                bmd2getnthmoment(handle, CUInt(MomentIndex), CUInt(sb.Capacity), y, m, d, h, mm, s, elapsed, sb, errcode)
                If errcode <> enumError.None Then Throw New BMD2Exception
                Return SeedMoment.AddDays(elapsed)
                'Return New DateTime(CInt(y), CInt(m), CInt(d), CInt(h), CInt(mm), CInt(s)) 'returns incorrect date of 2/29/2100
            End Get
        End Property

        ''' <summary>
        ''' Return list of all moments at which data are available
        ''' </summary>
        Public ReadOnly Property Moments As List(Of DateTime)
            Get
                Static lstMoments As List(Of DateTime) = Nothing
                If lstMoments Is Nothing Then
                    lstMoments = New List(Of DateTime)
                    For i As Integer = 0 To NumMoments - 1
                        lstMoments.Add(NthMoment(i))
                    Next
                End If
                Return lstMoments
            End Get
        End Property

        Public Const METADATA_SEGMENT_NAME = "NAME"
        Public Const METADATA_SEGMENT_INDEX = "INDEX"
        Public Const METADATA_SEGMENT_DESCRIPTION = "DESCRIPTION"
        Public Const METADATA_SEGMENT_BRANCH = "BRANCH"
        Public Const METADATA_SEGMENT_RIVERMILE = "RIVERMILE"
        Public Const METADATA_SEGMENT_I = "I"
        Public Const METADATA_SEGMENT_J = "J"
        Public Const METADATA_SEGMENT_K = "K"
        Public Const METADATA_SEGMENT_X = "X"
        Public Const METADATA_SEGMENT_Y = "Y"
        Public Const METADATA_SEGMENT_Z = "Z"
        Public Const METADATA_SEGMENT_DX = "DX"
        Public Const METADATA_SEGMENT_DY = "DY"
        Public Const METADATA_SEGMENT_DZ = "DZ"

        ''' <summary>
        ''' Segment metadata are associated with the segment; this is the general purpose call and you may include predefined keys like METADATA_SEGMENT_*; see also special purpose properties for retrieving name, etc.
        ''' </summary>
        ''' <param name="SegmentIndex">Index of segment</param>
        ''' <value>Dictionary of string keys and values</value>
        Public ReadOnly Property SegmentMetaData(SegmentIndex As Integer) As Dictionary(Of String, String)
            Get
                Dim m As New Dictionary(Of String, String)
                Dim num As UInt32 = 0
                bmd2getnumsegmentmetadata(handle, CUInt(SegmentIndex), num, errcode)
                If errcode <> enumError.None Then Throw New BMD2Exception
                If num > 0 Then
                    For i As UInt32 = 0 To CUInt(num - 1)
                        Dim sbKey As New System.Text.StringBuilder(512)
                        Dim sbValue As New System.Text.StringBuilder(512)
                        bmd2getnthsegmentmetadata(handle, CUInt(SegmentIndex), i, CUInt(sbKey.Capacity), CUInt(sbValue.Capacity), sbKey, sbValue, errcode)
                        If errcode <> enumError.None Then Throw New BMD2Exception
                        m.Add(sbKey.ToString, sbValue.ToString)
                    Next
                End If
                Return m
            End Get
        End Property

        ''' <summary>
        ''' Segment metadata are associated with the segment; this is the general purpose call and you may include predefined keys like METADATA_SEGMENT_*; see also special purpose properties for retrieving name, etc.
        ''' </summary>
        ''' <param name="SegmentIndex">Index of segment</param>
        ''' <param name="Key">Key to assign value to</param>
        ''' <value>Value to assign to key</value>
        ''' <remarks>When creating a file, all calls to set metadata should be made before the first call to SetNextFrame</remarks>
        Public WriteOnly Property SegmentMetaData(SegmentIndex As Integer, Key As String) As String
            Set(value As String)
                Dim sbKey As New System.Text.StringBuilder(Key & Chr(0), 512)
                Dim sbValue As New System.Text.StringBuilder(value & Chr(0), 512)
                bmd2setsegmentmetadata(handle, CUInt(SegmentIndex), sbKey, sbValue, errcode)
                If errcode <> enumError.None Then Throw New BMD2Exception
            End Set
        End Property

        Public Const METADATA_VARIABLE_NAME = "NAME"
        Public Const METADATA_VARIABLE_DESCRIPTION = "DESCRIPTION"
        Public Const METADATA_VARIABLE_UNITS = "UNITS"

        ''' <summary>
        ''' Variable metadata are associated with the variable; this is the general purpose call and you may include predefined keys like METADATA_VARIABLE_*; see also special purpose properties for retrieving name, etc.
        ''' </summary>
        ''' <param name="VariableIndex">Index of variable</param>
        ''' <value>Dictionary of string keys and values</value>
        Public ReadOnly Property VariableMetaData(VariableIndex As Integer) As Dictionary(Of String, String)
            Get
                Dim m As New Dictionary(Of String, String)
                Dim num As UInt32 = 0
                bmd2getnumvariablemetadata(handle, CUInt(VariableIndex), num, errcode)
                If errcode <> enumError.None Then Throw New BMD2Exception
                If num > 0 Then
                    For i As UInt32 = 0 To CUInt(num - 1)
                        Dim sbKey As New System.Text.StringBuilder(512)
                        Dim sbValue As New System.Text.StringBuilder(512)
                        bmd2getnthvariablemetadata(handle, CUInt(VariableIndex), i, CUInt(sbKey.Capacity), CUInt(sbValue.Capacity), sbKey, sbValue, errcode)
                        If errcode <> enumError.None Then Throw New BMD2Exception
                        m.Add(sbKey.ToString, sbValue.ToString)
                    Next
                End If
                Return m
            End Get
        End Property

        ''' <summary>
        ''' Variable metadata are associated with the variable; this is the general purpose call and you may include predefined keys like METADATA_VARIABLE_*; see also special purpose properties for retrieving name, etc.
        ''' </summary>
        ''' <param name="VariableIndex">Index of variable</param>
        ''' <param name="Key">Key to write</param>
        ''' <value>Value to assign to key</value>
        ''' <remarks>When creating a file, all calls to set metadata should be made before the first call to SetNextFrame</remarks>
        Public WriteOnly Property VariableMetaData(VariableIndex As Integer, Key As String) As String
            Set(value As String)
                Dim sbKey As New System.Text.StringBuilder(Key & Chr(0), 512)
                Dim sbValue As New System.Text.StringBuilder(value & Chr(0), 512)
                bmd2setvariablemetadata(handle, CUInt(VariableIndex), sbKey, sbValue, errcode)
                If errcode <> enumError.None Then Throw New BMD2Exception
            End Set
        End Property

        ''' <summary>
        ''' Return index of segment having specified name (is case sensitive)
        ''' </summary>
        ''' <param name="Name">Name of segment</param>
        ''' <returns>Index of segment</returns>
        Public ReadOnly Property SegmentIndex(Name As String) As Integer
            Get
                Dim sb As New System.Text.StringBuilder(Name & Chr(0), 512)
                Dim index As UInt32 = 0
                bmd2getsegindexbyname(handle, sb, index, errcode)
                If errcode <> enumError.None Then Throw New BMD2Exception
                Return CInt(index)
            End Get
        End Property

        ''' <summary>
        ''' Return index of variable having specified name (is case sensitive)
        ''' </summary>
        ''' <param name="Name">Name of variable</param>
        ''' <returns>Index of variable</returns>
        Public ReadOnly Property VariableIndex(Name As String) As Integer
            Get
                Dim sb As New System.Text.StringBuilder(Name & Chr(0), 512)
                Dim index As UInt32 = 0
                bmd2getvarindexbyname(handle, sb, index, errcode)
                If errcode <> enumError.None Then Throw New BMD2Exception
                Return CInt(index)
            End Get
        End Property

        ''' <summary>
        ''' Return closest index of specified moment
        ''' </summary>
        ''' <param name="Moment">Date-time of desired moment</param>
        ''' <returns>Index of moment</returns>
        Public ReadOnly Property MomentIndex(Moment As DateTime) As Integer
            Get
                Static _moments() As DateTime = Nothing
                If _moments Is Nothing Then _moments = Moments.ToArray()
                If Moment < _moments(0) Then Return 0
                If Moment > _moments(NumMoments - 1) Then Return NumMoments - 1
                Dim RecNum As Integer = Array.BinarySearch(_moments, 0, NumMoments, Moment)
                If RecNum < 0 Then RecNum = RecNum Xor -1 'not found, returns bitwise element of next larger value
                If RecNum > NumMoments - 1 Then Return NumMoments - 1 'round-off may make it slightly higher and cause index error
                If RecNum > 0 Then 'see if prior one in array is actually closer in value
                    If Math.Abs(Moment.Subtract(_moments(RecNum - 1)).TotalDays) < Math.Abs(Moment.Subtract(_moments(RecNum)).TotalDays) Then RecNum -= 1
                End If
                Return RecNum
            End Get
        End Property

        ''' <summary>
        ''' Segment name, stored in segment metadata
        ''' </summary>
        Public Property SegmentName(index As Integer) As String
            Get
                Dim s As String = ""
                SegmentMetaData(index).TryGetValue(METADATA_SEGMENT_NAME, s)
                Return CType(TestNull(s, ""), String)
            End Get
            Set(value As String)
                SegmentMetaData(index, METADATA_SEGMENT_NAME) = value
            End Set
        End Property

        ''' <summary>
        ''' Return list of segment names
        ''' </summary>
        Public ReadOnly Property SegmentNames As List(Of String)
            Get
                Dim lst As New List(Of String)
                For i = 0 To NumSegments - 1
                    lst.Add(SegmentName(i))
                Next
                Return lst
            End Get
        End Property

        ''' <summary>
        ''' Variable name, stored in variable metadata
        ''' </summary>
        Public Property VariableName(index As Integer) As String
            Get
                Dim s As String = ""
                VariableMetaData(index).TryGetValue(METADATA_VARIABLE_NAME, s)
                Return CType(TestNull(s, ""), String)
            End Get
            Set(value As String)
                VariableMetaData(index, METADATA_VARIABLE_NAME) = value
            End Set
        End Property

        ''' <summary>
        ''' Return list of variable names
        ''' </summary>
        Public ReadOnly Property VariableNames As List(Of String)
            Get
                Dim lst As New List(Of String)
                For i = 0 To NumVariables - 1
                    lst.Add(VariableName(i))
                Next
                Return lst
            End Get
        End Property

        ''' <summary>
        ''' Variable units, stored in variable metadata
        ''' </summary>
        Public Property VariableUnits(index As Integer) As String
            Get
                Dim s As String = ""
                VariableMetaData(index).TryGetValue(METADATA_VARIABLE_UNITS, s)
                Return CType(TestNull(s, ""), String)
            End Get
            Set(value As String)
                VariableMetaData(index)(METADATA_VARIABLE_UNITS) = value
            End Set
        End Property

        ''' <summary>
        ''' Set the values for all segments and constituents for the next timestep
        ''' </summary>
        ''' <param name="DateTime">The moment associated the data to be written</param>
        ''' <param name="Data">A 2-D array of values; first index is variable number, second is segment number)</param>
        Public Sub SetNextFrame(DateTime As DateTime, Data As Single(,))
            Static NumSeg As Integer = NumSegments
            Static NumVar As Integer = NumVariables
            Dim moment As Double = GetMoment(DateTime)
            If Data.GetLength(0) <> NumVar OrElse Data.GetLength(1) <> NumSeg Then
                Throw New ApplicationException("Invalid array dimensions passed to SetNextFrame.")
            End If
            bmd2setnextframe(handle, moment, Data, errcode)
            If errcode <> enumError.None Then Throw New BMD2Exception
        End Sub

        ''' <summary>
        ''' Initiate data frames. Call this function after setting all metadata and prior to calling SetNextFrame
        ''' </summary>
        Public Sub BeginFrames()

            Debug.Print("start beginframes")

            bmd2beginframes(handle, errcode)
            If errcode <> enumError.None Then Throw New BMD2Exception

            Debug.Print("end beginframes")

        End Sub

        ''' <summary>
        ''' The last frame has been sent and optimization will now begin (if OPTIMIZATION_ONCLOSE specified)
        ''' </summary>
        Public Sub EndFrames()
            Debug.Print("start endframes")
            RaiseEvent Progress("Optimizing file...", 0)
            bmd2endframes(handle, errcode)
            If errcode <> enumError.None Then Throw New BMD2Exception
            'If Not IsOptimized() Then Exit Sub 'library throws unhandled error
            Dim pct As Integer = 0
            Do
                If isCancelled Then
                    Debug.Print("Cancelling optimization...")
                    bmd2canceloptimization(handle, errcode)
                    If errcode <> enumError.None Then Throw New BMD2Exception
                    Exit Do
                End If
                Dim progress As UInt32
                bmd2getprogress(handle, progress, errcode)
                If progress <> pct Then
                    Debug.Print(progress.ToString)
                    pct = CInt(progress)
                    'RaiseEvent Progress("Optimizing file...", pct)
                End If
                Threading.Thread.Sleep(100)
                RaiseEvent Progress("Optimizing file...", pct)
            Loop Until pct = 100
            Debug.Print("end endframes")
        End Sub

        ''' <summary>
        ''' Get the values for all segments and constituents for the specified moment; this is 2-D array of values where the first index references the variable number, and the second the segment number
        ''' </summary>
        ''' <param name="MomentIndex">Index of the moment to be retrieved</param>
        ''' <returns>A 2-D array of values; first index is variable number, second is segment number</returns>
        Public ReadOnly Property DataAtTime(MomentIndex As Integer) As Single(,)
            Get
                Dim m = CUInt(MomentIndex)
                Dim moment As Double = 0.0
                Dim data(NumVariables - 1, NumSegments - 1) As Single
                bmd2getdataattime(handle, m, moment, data, errcode)
                If errcode <> enumError.None Then Throw New BMD2Exception : Return Nothing
                Return data
            End Get
        End Property

        ''' <summary>
        ''' Return time series data for a particular segment and variable
        ''' </summary>
        ''' <param name="SegmentIndex">Index of segment</param>
        ''' <param name="VariableIndex">Index of variable</param>
        ''' <returns>Dictionary of date-times and associated values</returns>
        ''' <remarks></remarks>
        Public Function DataOverTime(SegmentIndex As Integer, VariableIndex As Integer) As Single()
            Dim DataX(NumMoments - 1) As Double
            Dim DataY(NumMoments - 1) As Single
            bmd2getdataovertime(handle, CUInt(SegmentIndex), CUInt(VariableIndex), DataX, DataY, errcode)
            If errcode <> enumError.None Then Throw New BMD2Exception
            Return DataY
        End Function

        ''' <summary>
        ''' Return data for a particular variable and time where X values are taken from metadata
        ''' </summary>
        ''' <param name="VariableIndex">Index of variable</param>
        ''' <param name="MomentIndex">Index of moment</param>
        ''' <param name="DomainMetadataKey">Name of segment metadata key that contains numeric values to be used for x data; if pass INDEX, will return the segment index</param>
        ''' <param name="SegmentFilter">Filter in form of KEY1=FILTER1,KEY2=FILTER2,... for which data pairs will be returned; if empty string all segments are returned</param>
        ''' <returns>Dictionary of metadata values and associated numeric values</returns>
        ''' <remarks></remarks>
        Public Function DataOverSegment(VariableIndex As Integer, MomentIndex As Integer, DomainMetadataKey As String, SegmentFilter As String) As Dictionary(Of Single, Single)
            Dim DataX(NumSegments - 1) As Single
            Dim DataY(NumSegments - 1) As Single
            Dim sbKey As New System.Text.StringBuilder(DomainMetadataKey & Chr(0), 512)
            Dim sbFilter As New System.Text.StringBuilder(SegmentFilter & Chr(0), 512)
            Dim num As UInt32
            bmd2getdataoversegment(handle, CUInt(VariableIndex), CUInt(MomentIndex), sbKey, sbFilter, DataX, DataY, num, errcode)
            If errcode <> enumError.None Then Throw New BMD2Exception
            Dim dict As New Dictionary(Of Single, Single)
            For i As Integer = 0 To CInt(num) - 1
                dict.Add(DataX(i), DataY(i))
            Next
            Return dict
        End Function

        ''' <summary>
        ''' Get minimum and maximum values for specified variable over all segments and times
        ''' </summary>
        ''' <param name="VariableIndex">Index of variable</param>
        ''' <param name="Min">Minimum value</param>
        ''' <param name="Max">Maximum value</param>
        Public Sub MinMaxForVariable(VariableIndex As Integer, ByRef Min As Single, ByRef Max As Single)
            bmd2getminmaxforvariable(handle, CUInt(VariableIndex), Min, Max, errcode)
            If errcode <> enumError.None Then Throw New BMD2Exception
        End Sub

        ''' <summary>
        ''' Get minimum and maximum values for specified variable and segment over all times
        ''' </summary>
        ''' <param name="SegmentIndex">Index of segment</param>
        ''' <param name="VariableIndex">Index of variable</param>
        ''' <param name="Min">Minimum value</param>
        ''' <param name="Max">Maximum value</param>
        Public Sub MinMaxForSegVar(SegmentIndex As Integer, VariableIndex As Integer, ByRef Min As Single, ByRef Max As Single)
            bmd2getminmaxforsegvar(handle, CUInt(SegmentIndex), CUInt(VariableIndex), Min, Max, errcode)
            If errcode <> enumError.None Then Throw New BMD2Exception
        End Sub

#End Region

    End Class
End Namespace
